package torchtools.io;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import torchtools.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * Attempts to load icons into a hashmap.
 * <p>
 * If no icons are found, it will copy the imagesets in the
 * TorchED directory into individual files. If no TorchED directory
 * is found, the tool will throw an exception.
 * 
 * @author Matt
 */
public class IconLoader {
    
    
    /** 
     
     TODO: to reduce memory use... Just load imagesets into memory, each as
     a buffered image. then use paint() methods on InventoryCanvas 
     * 
     Or use subImages, which all share the same data array as the original.
     */
    
    private AppContext context;
    
    /** A table of image definitions. */
    private HashMap<String, TLIcon> iconMap = new HashMap<String, TLIcon>();
    
    private List<TLImageSet> imageSets;
    
    private List<String> imageSetIgnores;
    private FilenameFilter filter = new ImageSetFilter();
    
    public IconLoader(AppContext context) {
        this.context = context;
    }
    
    /** Reads an XML file, returning the root element. */
    public TLImageSet readImageSet(File imageset) throws IOException {
        try {
            Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(imageset);
            Element rootEl = (Element)doc.getDocumentElement();
            if (!"Imageset".equalsIgnoreCase(rootEl.getNodeName()))
                throw new IOException("not a valid .imageset file");
            
            //create the root node
            return readImageSetEl(rootEl, imageset);
        } catch (SAXException ex) {
            ex.printStackTrace();
        } catch (ParserConfigurationException ex) {
            ex.printStackTrace();
        }
        return null;
    }
    
    /** Lookup ImageDef by name, return the set it belongs to. */
    public TLIcon getIcon(String imageName) {
        return iconMap.get(imageName);
    }
    
    private int intatt(Element el, String att, int def) {
        try { return Integer.parseInt(el.getAttribute(att)); }
        catch (NumberFormatException e) { return def; }
    }
    
    private int intatt(Element el, String att) { 
        return intatt(el, att, 0); 
    }
    
    private TLImageSet readImageSetEl(Element root, File imageset) throws IOException {
        //read the header
        String Name = root.getAttribute("Name");
        String Imagefile = root.getAttribute("Imagefile");
        int NativeHorzRes = intatt(root, "NativeHorzRes", 1024);
        int NativeVertRes = intatt(root, "NativeVertRes", 768);
        boolean AutoScaled = Boolean.parseBoolean(root.getAttribute("AutoScaled"));
        
        //read the .png image, which has the same name as the imageset but with
        //a different extension
        File filePNG = file2ext(imageset, "png");
        String ImagefilePNG = filePNG.getPath();
        BufferedImage Sheet = ImageIO.read(filePNG);
        
        //set this up now so we can initialize TLImageSet
        ArrayList<TLIcon> icons = new ArrayList<TLIcon>();
        
        TLImageSet Set = new TLImageSet(Sheet, Name, Imagefile, NativeHorzRes, NativeVertRes, AutoScaled, ImagefilePNG, icons);
        
        //read each icon
        NodeList nl = root.getElementsByTagName("Image");
        for (int i=0; i<nl.getLength(); i++) {
            //read each <Image>
            Element e = (Element)nl.item(i);
            String IName = e.getAttribute("Name");
            int XPos = intatt(e, "XPos");
            int YPos = intatt(e, "YPos");
            int Width = intatt(e, "Width");
            int Height = intatt(e, "Height");
            TLIcon icon = new TLIcon(Set, IName, XPos, YPos, Width, Height);
            icons.add(icon);
            iconMap.put(IName, icon);
        }
        
        icons.trimToSize();
        
        return Set;
    }
    
    /** 
     * Imagesets are ignored if specified in settings.cfg or if they have
     * already been loaded. 
     */
    private boolean isIgnored(String filename) {
        for (int i=0; i<imageSetIgnores.size(); i++) {
            if (filename.equalsIgnoreCase(imageSetIgnores.get(i)))
                return true;
        }
        return false;
    }
    
    /** Combines an array of files. */
    private static File[] combine(File[] a, File[] b) {
        final int alen = a.length;
        final int blen = b.length;
        if (alen == 0)
            return b;
        if (blen == 0)
            return a;
        final File[] result = new File[alen+blen];
        System.arraycopy(a, 0, result, 0, alen);
        System.arraycopy(b, 0, result, alen, blen);
        return result;
    }
    
    private File file2ext(File file, String ext) {
        String s = file.getPath();
        int i = s.indexOf('.');
        if (i==-1)
            return new File(s+'.'+ext);
        return new File(s.substring(0, i+1)+ext);
    }
    
    public void load() throws IOException {
        load(false);
    }
    
    /** Allows for re-loading the itemicons. */
    public void load(boolean overwrite) throws IOException {
        imageSetIgnores = new ArrayList<String>();
        for (String ig : context.getSettings().getImageSetIgnores())
            imageSetIgnores.add(ig);
        
        File iconsDir = new File("itemicons");
        if (!iconsDir.exists())
            iconsDir.mkdir();
                
        File[] files = iconsDir.listFiles(filter);
        
        //grab any remaining imagesets from TorchED folder
        File torchedMedia = new File(context.getTorchEDPath()+"\\media\\UI\\itemicons");
        if (torchedMedia.exists()) {
            File[] remaining = torchedMedia.listFiles(filter);
            
            //copy the TorchED itemicons folder to here
            for (int i=0; i<remaining.length; i++) {
                IOUtil.copyToDir(remaining[i], iconsDir, overwrite);
                IOUtil.copyToDir(file2ext(remaining[i], "png"), iconsDir, overwrite);
            }
            
            files = combine(files, remaining);
        }
        
        //now load each imageset
        imageSets = new ArrayList<TLImageSet>();
        for (int i=0; i<files.length; i++) {
            imageSets.add(readImageSet(files[i]));
        }
    }
    
    //TODO: needs fixing.. doesn't check for imageset AND png.. only imageset
    
    public static void main(String[] args) throws Exception {
        final Settings settings = new Settings() {
            public String[] getImageSetIgnores() {
                return new String[] { "itemicons15.imageset", "itemicons16.imageset" };
            }
            public boolean isModIconsEnabled() { return false; }  
        };
        
        final AppContext context = new AppContext() {
            public String getDataPath() {
                return "C:\\Users\\Matt\\AppData\\Roaming\\runic games\\torchlight";
            }
            public String getModPath() {
                return "C:\\Users\\Matt\\AppData\\Roaming\\runic games\\torchlight\\mods";
            }
            public String getExePath() {return null;}
            public String getTorchEDPath() { return "C:\\Program Files\\Runic Games\\TorchED\\"; }
            public Settings getSettings() {return settings;}
            
        };
        
        IconLoader loader = new IconLoader(context);
        loader.load();
        
        System.out.println(loader.getIcon("spell_cure").Set.Name);
        
        
    }
    
    private class ImageSetFilter implements FilenameFilter {
        public boolean accept(File dir, String name) {
            //if it's ignored, don't add it...
            if (isIgnored(name))
                return false;
            //ignore it next time around
            IconLoader.this.imageSetIgnores.add(name);
            //if it's .imageset extension, go for it
            int i = name.indexOf('.');
            if (i!=-1) {
                String ext = name.substring(i+1, name.length());
                return ext.toLowerCase().equals(TLImageSet.FILE_EXT);
            }
            return false;
        }
    }
}
